package com.jzo2o.orders.manager.service.impl;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.api.customer.AddressBookApi;
import com.jzo2o.api.customer.dto.response.AddressBookResDTO;
import com.jzo2o.api.foundations.dto.response.ServeAggregationResDTO;
import com.jzo2o.api.market.CouponApi;
import com.jzo2o.api.market.dto.request.CouponUseReqDTO;
import com.jzo2o.api.market.dto.response.AvailableCouponsResDTO;
import com.jzo2o.api.market.dto.response.CouponUseResDTO;
import com.jzo2o.api.trade.NativePayApi;
import com.jzo2o.api.trade.TradingApi;
import com.jzo2o.api.trade.dto.request.NativePayReqDTO;
import com.jzo2o.api.trade.dto.response.NativePayResDTO;
import com.jzo2o.api.trade.dto.response.TradingResDTO;
import com.jzo2o.api.trade.enums.PayChannelEnum;
import com.jzo2o.api.trade.enums.TradingStateEnum;
import com.jzo2o.common.expcetions.BadRequestException;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.common.model.msg.TradeStatusMsg;
import com.jzo2o.common.utils.BeanUtils;
import com.jzo2o.common.utils.DateUtils;
import com.jzo2o.common.utils.ObjectUtils;
import com.jzo2o.mvc.utils.UserContext;
import com.jzo2o.orders.base.config.OrderStateMachine;
import com.jzo2o.orders.base.enums.OrderPayStatusEnum;
import com.jzo2o.orders.base.enums.OrderStatusChangeEventEnum;
import com.jzo2o.orders.base.enums.OrderStatusEnum;
import com.jzo2o.orders.base.mapper.OrdersMapper;
import com.jzo2o.orders.base.model.domain.Orders;
import com.jzo2o.orders.base.model.dto.OrderSnapshotDTO;
import com.jzo2o.orders.manager.model.dto.request.OrdersPayReqDTO;
import com.jzo2o.orders.manager.model.dto.request.PlaceOrderReqDTO;
import com.jzo2o.orders.manager.model.dto.response.OrdersPayResDTO;
import com.jzo2o.orders.manager.model.dto.response.PlaceOrderResDTO;
import com.jzo2o.orders.manager.porperties.TradeProperties;
import com.jzo2o.orders.manager.service.IOrdersCreateService;
import com.jzo2o.orders.manager.service.client.FoundationsClient;
import com.jzo2o.orders.manager.service.client.MarketClient;
import com.jzo2o.redis.annotations.Lock;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;

import static com.jzo2o.orders.base.constants.RedisConstants.Lock.ORDERS_SHARD_KEY_ID_GENERATOR;

/**
 * <p>
 * 下单服务类
 * </p>
 *
 * @author itcast
 * @since 2023-07-10
 */
@Slf4j
@Service
public class OrdersCreateServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements IOrdersCreateService {
    @Resource
    private FoundationsClient foundationsClient;
    @Resource
    private AddressBookApi addressBookApi;
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private IOrdersCreateService owner;
    @Resource
    private TradeProperties tradeProperties;
    @Resource
    private NativePayApi nativePayApi;
    @Resource
    private TradingApi tradingApi;
    @Resource
    private OrderStateMachine orderStateMachine;
    @Resource
    private MarketClient marketClient;
    @Resource
    private CouponApi couponApi;

    @Override
    public PlaceOrderResDTO place(PlaceOrderReqDTO placeOrderReqDTO) {
        Long userId = UserContext.currentUserId();
        long serveStartTime = LocalDateTimeUtil.toEpochMilli(placeOrderReqDTO.getServeStartTime());
        return owner.place(userId, serveStartTime, placeOrderReqDTO);
    }

    @Override
    @Lock(formatter = "ORDERS:CREATE:LOCK:#{userId}:#{placeOrderReqDTO.serveId}:#{serveStartTime}", time = 5, unlock = false)
    public PlaceOrderResDTO place(Long userId, long serveStartTime, PlaceOrderReqDTO placeOrderReqDTO) {
        // 下单人信息，获取地址簿，调用jzo2o-customer服务获取
        AddressBookResDTO addressBookResDTO = addressBookApi.detail(placeOrderReqDTO.getAddressBookId());
        if (ObjectUtils.isNull(addressBookResDTO)) {
            throw new BadRequestException("地址获取失败");
        }
        // 服务相关信息,调用jzo2o-foundations获取
        ServeAggregationResDTO serveAggregationResDTO = foundationsClient.findById(placeOrderReqDTO.getServeId());
        if (ObjectUtils.isNotNull(serveAggregationResDTO) && serveAggregationResDTO.getId() < 0) {
            throw new CommonException("远程调用服务接口超时");
        }
        if (ObjectUtils.isNull(serveAggregationResDTO) || serveAggregationResDTO.getSaleStatus() != 2) {
            throw new BadRequestException("服务不存在或未上架");
        }

        // 组装订单信息
        Orders orders = new Orders();
        // 生成订单号
        Long orderId = generateOrderId();
        orders.setId(orderId);
        orders.setUserId(userId);
        orders.setServeTypeId(serveAggregationResDTO.getServeTypeId());
        orders.setServeTypeName(serveAggregationResDTO.getServeTypeName());
        orders.setServeItemId(serveAggregationResDTO.getServeItemId());
        orders.setServeItemName(serveAggregationResDTO.getServeItemName());
        orders.setServeItemImg(serveAggregationResDTO.getServeItemImg());
        orders.setUnit(serveAggregationResDTO.getUnit());
        orders.setServeId(serveAggregationResDTO.getId());
        orders.setOrdersStatus(OrderStatusEnum.NO_PAY.getStatus());
        orders.setPayStatus(OrderPayStatusEnum.NO_PAY.getStatus());
        BigDecimal price = serveAggregationResDTO.getPrice();
        Integer purNum = placeOrderReqDTO.getPurNum();
        orders.setPrice(price);
        orders.setPurNum(purNum);
        // 计算价格
        BigDecimal totalAmount = price.multiply(BigDecimal.valueOf(purNum));
        orders.setTotalAmount(totalAmount);
        orders.setCityCode(serveAggregationResDTO.getCityCode());
        String serveAddress = new StringBuffer().append(addressBookResDTO.getCounty())
                .append(addressBookResDTO.getCity())
                .append(addressBookResDTO.getProvince())
                .append(addressBookResDTO.getAddress()).toString();
        orders.setServeAddress(serveAddress);
        orders.setContactsPhone(addressBookResDTO.getPhone());
        orders.setContactsName(addressBookResDTO.getName());
        orders.setServeStartTime(placeOrderReqDTO.getServeStartTime());
        orders.setDisplay(1);
        orders.setSortBy(serveStartTime + orderId % 1000000);
        // 使用优惠券下单
        if (ObjectUtils.isNotNull(placeOrderReqDTO.getCouponId())) {
            // 使用优惠券
            owner.addWithCoupon(orders, placeOrderReqDTO.getCouponId());
        } else {
            // 无优惠券下单，走本地事务
            owner.add(orders);
        }
        return new PlaceOrderResDTO(orderId);
    }

    @Override
    public OrdersPayResDTO pay(Long id, OrdersPayReqDTO ordersPayReqDTO) {
        Orders orders = getById(id);
        if (ObjectUtils.isNull(orders)) {
            throw new CommonException("订单不存在");
        }
        if (ObjectUtils.equal(orders.getPayStatus(), OrderPayStatusEnum.PAY_SUCCESS.getStatus())
                && ObjectUtils.isNotNull(orders.getTradingOrderNo())) {
            OrdersPayResDTO ordersPayResDTO = BeanUtils.toBean(orders, OrdersPayResDTO.class);
            ordersPayResDTO.setProductOrderNo(orders.getId());
            return ordersPayResDTO;
        }
        NativePayReqDTO nativePayReqDTO = new NativePayReqDTO();
        PayChannelEnum tradingChannel = ordersPayReqDTO.getTradingChannel();
        nativePayReqDTO.setTradingChannel(tradingChannel);
        Long enterpriseId = ObjectUtils.equal(tradingChannel, PayChannelEnum.ALI_PAY)
                ? tradeProperties.getAliEnterpriseId() : tradeProperties.getWechatEnterpriseId();
        nativePayReqDTO.setEnterpriseId(enterpriseId);
        // 判断是否切换支付渠道
        if (ObjectUtil.isNotEmpty(orders.getTradingChannel())
                && ObjectUtil.notEqual(orders.getTradingChannel(), tradingChannel.toString())) {
            nativePayReqDTO.setChangeChannel(true);
        }
        nativePayReqDTO.setProductAppId("jzo2o.orders");
        nativePayReqDTO.setProductOrderNo(id);
        nativePayReqDTO.setTradingAmount(orders.getRealPayAmount());
        nativePayReqDTO.setMemo(orders.getServeItemName());
        NativePayResDTO nativePayResDTO = nativePayApi.createDownLineTrading(nativePayReqDTO);
        // 订单状态更新
        boolean update = this.lambdaUpdate().eq(Orders::getId, id)
                .set(Orders::getTradingChannel, tradingChannel.toString())
                .set(Orders::getTradingOrderNo, nativePayResDTO.getTradingOrderNo())
                .update();
        if (!update) {
            throw new CommonException("订单:" + orders.getId() + "请求支付更新交易单号失败");
        }
        // 返回二维码
        OrdersPayResDTO ordersPayResDTO = BeanUtils.toBean(nativePayResDTO, OrdersPayResDTO.class);
        ordersPayResDTO.setPayStatus(2);
        return ordersPayResDTO;
    }

    @Override
    public OrdersPayResDTO getPayResultFromTradServer(Long id) {
        Orders orders = this.getById(id);
        if (ObjectUtils.isNull(orders)) {
            throw new CommonException("订单号不存在");
        }
        if (ObjectUtils.equal(orders.getPayStatus(), OrderPayStatusEnum.NO_PAY.getStatus())
                && ObjectUtils.isNotNull(orders.getTradingOrderNo())) {
            TradingResDTO tradingResDTO = tradingApi.findTradResultByTradingOrderNo(orders.getTradingOrderNo());
            if (ObjectUtils.isNotNull(tradingResDTO) && ObjectUtils.equal(tradingResDTO.getTradingState(), TradingStateEnum.YJS)) {
                TradeStatusMsg tradeStatusMsg = new TradeStatusMsg();
                tradeStatusMsg.setProductOrderNo(id);
                tradeStatusMsg.setTradingOrderNo(tradingResDTO.getTradingOrderNo());
                tradeStatusMsg.setTransactionId(tradingResDTO.getTransactionId());
                tradeStatusMsg.setTradingChannel(tradingResDTO.getTradingChannel());
                tradeStatusMsg.setStatusCode(TradingStateEnum.YJS.getCode());
                owner.paySuccess(tradeStatusMsg);
                OrdersPayResDTO ordersPayResDTO = BeanUtils.toBean(tradeStatusMsg, OrdersPayResDTO.class);
                ordersPayResDTO.setPayStatus(OrderPayStatusEnum.PAY_SUCCESS.getStatus());
                return ordersPayResDTO;
            }
        }
        OrdersPayResDTO ordersPayResDTO = new OrdersPayResDTO();
        ordersPayResDTO.setProductOrderNo(orders.getId());
        ordersPayResDTO.setTradingOrderNo(orders.getTradingOrderNo());
        ordersPayResDTO.setTradingChannel(orders.getTradingChannel());
        ordersPayResDTO.setPayStatus(orders.getPayStatus());
        return ordersPayResDTO;
    }

    @Override
    @Transactional
    public void paySuccess(TradeStatusMsg tradeStatusMsg) {
        if (ObjectUtils.isNull(tradeStatusMsg)) {
            throw new CommonException("交易状态消息为空");
        }
        Orders orders = this.getById(tradeStatusMsg.getProductOrderNo());
        if (ObjectUtils.isNull(orders)) {
            throw new CommonException("订单不存在");
        }
        if (ObjectUtils.notEqual(orders.getPayStatus(), OrderPayStatusEnum.NO_PAY.getStatus())) {
            log.info("订单支付状态并非待支付状态");
            return;
        }
        if (ObjectUtils.notEqual(orders.getOrdersStatus(), OrderStatusEnum.NO_PAY.getStatus())) {
            log.info("订单状态并非待支付状态");
            return;
        }
        if (ObjectUtils.isNull(tradeStatusMsg.getTransactionId())) {
            throw new CommonException("支付成功通知缺少第三方支付单号");
        }
        // 存入快照类
        OrderSnapshotDTO orderSnapshotDTO = new OrderSnapshotDTO();
        orderSnapshotDTO.setPayStatus(OrderPayStatusEnum.PAY_SUCCESS.getStatus());
        orderSnapshotDTO.setPayTime(LocalDateTime.now());
        orderSnapshotDTO.setTradingOrderNo(tradeStatusMsg.getTradingOrderNo());
        orderSnapshotDTO.setThirdOrderId(tradeStatusMsg.getTransactionId());
        orderSnapshotDTO.setTradingChannel(tradeStatusMsg.getTradingChannel());
        // 调用状态机更新订单状态
        orderStateMachine.changeStatus(orders.getUserId(),orders.getId().toString(), OrderStatusChangeEventEnum.PAYED, orderSnapshotDTO);
    }

    @Override
    public List<Orders> queryOverTimePayOrdersListByCount(int i) {
        return this.lambdaQuery().eq(Orders::getOrdersStatus, OrderStatusEnum.NO_PAY.getStatus())
                .lt(Orders::getCreateTime, LocalDateTime.now().minusMinutes(15))
                .last("limit" + i)
                .list();
    }

    @Override
    public List<AvailableCouponsResDTO> getCoupons(Long serveId, Integer purNum) {
        // 服务相关信息,调用jzo2o-foundations获取
        ServeAggregationResDTO serveAggregationResDTO = foundationsClient.findById(serveId);
        if (ObjectUtils.isNotNull(serveAggregationResDTO) && serveAggregationResDTO.getId() < 0) {
            throw new CommonException("远程调用服务接口超时");
        }
        if (ObjectUtils.isNull(serveAggregationResDTO) || serveAggregationResDTO.getSaleStatus() != 2) {
            throw new BadRequestException("服务不存在或未上架");
        }
        return marketClient.getAvailable(serveAggregationResDTO.getPrice().multiply(BigDecimal.valueOf(purNum)));
    }


    @Override
    @GlobalTransactional(rollbackFor = Exception.class)
    public void addWithCoupon(Orders orders, Long couponId) {
        CouponUseReqDTO couponUseReqDTO = new CouponUseReqDTO();
        couponUseReqDTO.setId(couponId);
        couponUseReqDTO.setOrdersId(orders.getId());
        BigDecimal totalAmount = orders.getTotalAmount();
        couponUseReqDTO.setTotalAmount(totalAmount);
        CouponUseResDTO couponUseResDTO = couponApi.use(couponUseReqDTO);
        BigDecimal discountAmount = couponUseResDTO.getDiscountAmount();
        orders.setDiscountAmount(discountAmount);
        orders.setRealPayAmount(totalAmount.subtract(discountAmount));
        owner.add(orders);
    }

    /**
     * 生成订单id 格式：{1位类型}{yyMMdd}{12位id}
     *
     * @return 订单id
     */
    private Long generateOrderId() {
        // 通过Redis自增序列得到序号
        Long id = redisTemplate.opsForValue().increment(ORDERS_SHARD_KEY_ID_GENERATOR);
        // 生成订单号  1位类型+ 2位年+2位月+2位日+12位序号
        int type = 1;
        return (type * 1000000L + DateUtils.getFormatDate(LocalDateTime.now(), "yyMMdd")) * 1000000000000L + id;
    }

    @Override
    @Transactional
    public void add(Orders orders) {
        baseMapper.insert(orders);
        // 初始化状态机
        orders = this.getById(orders);
        OrderSnapshotDTO orderSnapshotDTO = BeanUtils.toBean(orders, OrderSnapshotDTO.class);
        orderStateMachine.start(orders.getUserId(), orders.getId().toString(), orderSnapshotDTO);
    }
}
